package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author caesar
 * @project-name:tingshu-server
 * @date 2025/10/24 17:09:11
 * @description: 切面类，进行登录校验
 * 具体细节：
 * 切面的内容是从前端返回的内容中获取到请求头中token，根据这个token进行校验
 * 同时对于接口的参数进行一个判断，
 * 参数为true，表示必须登录，那么这个token不能为空，token在redis中对应的数据不能为空，
 * 参数为false，表示登录也可以看，不登录也可以看，直接执行方法就可以
 * 因为其他的数据库表中有当前用户id这个字段
 * 所以需要我们提前从这里获取一下，存储到AuthContextHolder对象中
 * @apiNote：
 */

@Aspect //表示这个类是一个切面类
@Component  //表示这个类被添加到核心容器
public class GuiGuLoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    //思路：使用环绕通知的方式，进行登录校验
    //ProceedingJoinPoint:获取被增强方法信息和被增强方法执行
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiguLogin)")
    public Object login(ProceedingJoinPoint joinPoint,
                        GuiguLogin guiguLogin) throws Throwable {

        //从前端返回的内容中，获取到请求头，这里通过上下文对象获取
        //RequestContextHolder 上下文对象 获取request对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
        //获取到请求信息
        HttpServletRequest request = attributes.getRequest();
        //获取请求头的token
        //1、从请求头获取token（前端传递过来的）
        String token = request.getHeader("token");

        //先获取required，判断required为true还是false
        boolean isRequired = guiguLogin.required();
        if(isRequired){//必须登录
            //如果token为空，返回登录提示信息
            if(StringUtils.isEmpty(token)){
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            }
            //如果token不为空，根据token查询redis，判断查询数据是否为空，如果为空，返回登录提示信息
            UserInfo userInfo =
                    (UserInfo) redisTemplate.opsForValue().get(token);
            //如果为空，返回登录提示信息
            if(userInfo == null) {
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            }
        }

        //获取用户的id，通过AuthContextHolder存储，类似于request的作用范围，一次请求，请求结束失效
        if (!StringUtils.isEmpty(token)){
            //  组成缓存key，这里注意，这里的token和其他地方的token对应，要么都加前缀，要么都不加
            //String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX+token;
            //  获取缓存中用户数据
            UserInfo userInfo =
                    (UserInfo) this.redisTemplate.opsForValue().get(token);
            if (null != userInfo){
                //  存储用户Id
                AuthContextHolder.setUserId(userInfo.getId());
            }
        }
        //执行方法，返回值是一个result对象
        Object obj = joinPoint.proceed();
        return obj;
    }
}
